﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace SimpleORM
{
    public class LambdaTranslator : ExpressionVisitor
    {
        #region Properties

        #region Properties - Dependencies

        public IProviderDatabase DataSource { get; set; }
        public ObjectContextDictionary ObjectContextDictionary { get; set; }
        public FromNameDictionary FromNameDictionary { get; set; }

        #endregion

        #region Properties - Result

        public IScriptFactory Result { get; protected set; }

        #endregion

        #endregion

        #region Fields

        protected Stack<IScriptFactory> Stack { get; private set; }

        #endregion

        #region Ctor

        public LambdaTranslator()
        {
            Stack = new Stack<IScriptFactory>();
        }

        #endregion

        #region Methods

        #region Methods - Public

        public virtual void Translate(Expression expression)
        {
            Expression exp = Evaluator.PartialEval(expression);

            this.Visit(exp);

            if (Stack.Count > 0)
                Result = Stack.Pop();
        }

        #endregion

        #region Methods - Visit

        protected override Expression VisitBinary(BinaryExpression b)
        {
            this.Visit(b.Left);
            this.Visit(b.Right);

            IScriptFactory p1 = Stack.Pop();
            IScriptFactory p2 = Stack.Pop();

            switch (b.NodeType)
            {
                case ExpressionType.Equal:
                    p2 = p2.Equal(p1);
                    break;
                case ExpressionType.NotEqual:
                    p2 = p2.NotEqual(p1);
                    break;
                case ExpressionType.LessThan:
                    p2 = p2.LessThan(p1);
                    break;
                case ExpressionType.LessThanOrEqual:
                    p2 = p2.LessThanOrEqual(p1);
                    break;
                case ExpressionType.GreaterThan:
                    p2 = p2.GreaterThan(p1);
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    p2 = p2.GreaterThanOrEqual(p1);
                    break;
                case ExpressionType.AndAlso:
                    p2 = p2.And(p1);
                    break;
                case ExpressionType.OrElse:
                    p2 = p2.Or(p1);
                    break;
                case ExpressionType.Add:
                    p2 = p2.Add(p1);
                    break;
                case ExpressionType.Subtract:
                    p2 = p2.Subtract(p1);
                    break;
                case ExpressionType.Multiply:
                    p2 = p2.Multiply(p1);
                    break;
                case ExpressionType.Divide:
                    p2 = p2.Divide(p1);
                    break;
                default:
                    throw new NotSupportedException();
            }

            Stack.Push(p2);

            return b;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            this.Visit(m.Object);
            foreach (var arg in m.Arguments)
                this.Visit(arg);
            //this.Visit(arg is LambdaExpression ? ((LambdaExpression)arg).Body : arg);

            IScriptFactory p1 = null;
            IScriptFactory p2 = null;
            IScriptFactory p3 = null;

            switch (m.Method.Name)
            {
                case "StartsWith":
                    p1 = Stack.Pop();
                    p2 = Stack.Pop();
                    p2 = p2.StartWith(p1);
                    break;
                case "Contains":
                    p1 = Stack.Pop();
                    p2 = Stack.Pop();
                    p2 = p2.Contains(p1);
                    break;
                case "EndsWith":
                    p1 = Stack.Pop();
                    p2 = Stack.Pop();
                    p2 = p2.EndWith(p1);
                    break;
                case "Equals":
                    p1 = Stack.Pop();
                    p2 = Stack.Pop();
                    p2 = p2.Equal(p1);
                    break;
                case "Average":
                    p1 = Stack.Pop();
                    p2 = DataSource.CreateScriptFactory().Average(p1);
                    p2.CommandText = p2.CommandText.Remove(0, 7);
                    break;
                case "Sum":
                    p1 = Stack.Pop();
                    p2 = DataSource.CreateScriptFactory().Sum(p1);
                    p2.CommandText = p2.CommandText.Remove(0, 7);
                    break;
                case "Max":
                    p1 = Stack.Pop();
                    p2 = DataSource.CreateScriptFactory().Max(p1);
                    p2.CommandText = p2.CommandText.Remove(0, 7);
                    break;
                case "Min":
                    p1 = Stack.Pop();
                    p2 = DataSource.CreateScriptFactory().Min(p1);
                    p2.CommandText = p2.CommandText.Remove(0, 7);
                    break;
                case "Count":
                    p2 = DataSource.CreateScriptFactory().Count();
                    p2.CommandText = p2.CommandText.Remove(0, 7);
                    break;
                case "Replace":
                    p1 = Stack.Pop();
                    p2 = Stack.Pop();
                    p3 = Stack.Pop();
                    p2 = p3.Replace(p2, p1);
                    break;
                case "Substring":
                    p1 = Stack.Pop();
                    p2 = Stack.Pop();
                    p3 = Stack.Pop();
                    p2 = p3.Substring(p2, p1);
                    break;
                case "ToString":
                    p1 = Stack.Pop();
                    p2 = p1.ConvertToString();
                    break;
                case "GetLiteralLength":
                    p1 = Stack.Pop();
                    p2 = DataSource.CreateScriptFactory().Len(p1);
                    break;
                case "Intersects":
                    p1 = Stack.Pop();
                    p2 = Stack.Pop();
                    p2 = p2.Intersect(p1);
                    break;
                case "Within":
                    p1 = Stack.Pop();
                    p2 = Stack.Pop();
                    p2 = p2.Within(p1);
                    break;
                case "Concat":
                    p1 = Stack.Pop();
                    p2 = Stack.Pop();
                    p2 = p2.Add(p1);
                    break;
                default:
                    throw new NotSupportedException();
            }

            Stack.Push(p2);

            return m;
        }

        protected override Expression VisitConditional(ConditionalExpression c)
        {
            this.Visit(c.Test);
            this.Visit(c.IfTrue);
            this.Visit(c.IfFalse);

            IScriptFactory p1 = Stack.Pop();
            IScriptFactory p2 = Stack.Pop();
            IScriptFactory p3 = Stack.Pop();

            Stack.Push(DataSource.CreateScriptFactory().If(p3, p2, p1));

            return c;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            ObjectContext oc = null;

            if (m.Expression.NodeType == ExpressionType.MemberAccess)
                oc = ObjectContextDictionary[(m.Expression as MemberExpression).Type];
            else if (m.Expression.NodeType == ExpressionType.Parameter)
                oc = ObjectContextDictionary[(m.Expression as ParameterExpression).Type];

            Stack.Push(DataSource.CreateScriptFactory().
                       Constant(FromNameDictionary[oc.ElementType]).
                       Stress().
                       Concat(DataSource.CreateScriptFactory().
                       Constant(oc.Columns[m.Member.Name].DataColumn.ColumnName).
                       Stress(), "."));

            return m;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            Stack.Push(DataSource.CreateScriptFactory().Parameter(c.Value));

            return c;
        }

        protected override NewExpression VisitNew(NewExpression n)
        {
            foreach (var item in n.Arguments)
                this.Visit(item);

            return n;
        }

        protected override Expression VisitMemberInit(MemberInitExpression init)
        {
            foreach (var item in init.Bindings)
                this.Visit((item as MemberAssignment).Expression);

            return init;
        }

        #endregion

        #endregion
    }
}
